package com.iamteer.day13;
/**
卖票模拟系统：线程安全问题

出现线程安全问题：
	1. 存在 2 个或以上的线程对象共享同一个资源
	2. 有多个语句操作了共享资源
	
线程安全问题解决方案：

	方式一：同步代码块
		格式：
			synchronized(锁对象){
				需要被同步的代码
			}
			
		注意：
			1. 任何一个对象都可以当锁
			2. 如果调用了 sleep 方法要等恢复之后才释放锁
			3. 只有存在线程安全问题时，才使用同步代码块，否则降低效率
			4. 锁对象必须唯一，否则无效
			
		优势：
			1. 锁对象可以自定义
			2. 可以方便控制需要被同步的代码范围
	
	方式二：同步函数（用 synchronized 修饰的函数）
		注意：
			1. 非静态同步函数的锁对象是 this 对象
			2. 静态同步函数的锁对象是当前函数所属的类的字节码文件（class 对象）
			3. 同步函数的锁对象是固定的

 */

class TM implements Runnable{
	
	int num = 50;	//可以不使用 static，因为本例中对象只创建一个
	
	@Override
	public void run() {
		while (true) {
			synchronized ("Lock") {
				if (num > 0) {
					System.out.println(Thread.currentThread().getName() + " selling the " + num + " ticket");
					num --;
				} else {
					System.out.println("Tickets sold out...");
					break;
				}
			}
		}
	}
}


class TicketMachine extends Thread{
	
//	int num = 50;			//每个对象会生成一份 50 张，共会卖出 200 张
	static int Num = 50;	//所有对象共享一份 50 张，但因“线程安全”问题，单靠它导致同一张票被多个站点卖出
//	static Object lock = new Object();	//唯一的锁对象，也可以直接用下面的 "Lock"
	
	public TicketMachine(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		super.run();
		
		while (Num > 0) {
			//方式一：同步代码块，把需要同步的代码放在 synchronized 里
/*			
 			synchronized ("Lock") {	//这字符串会在字符串常量池中，每次使用都是该地址，不会变，所以唯一	
				if(Num > 0){
					System.out.println(Thread.currentThread().getName() + "selling the " + Num + " ticket...");
					Num--;
				}else {
					System.out.println("Tickets sold out...");
					break;
				}
			}
*/
			//方式二：把需要同步的代码放在一个同步函数里
			sellTicket();
		}
	}
	//同步函数，内部的代码会被
	private static synchronized void sellTicket() {
		if(Num > 0){
			System.out.println(Thread.currentThread().getName() + " selling the " + Num + " ticket...");
			Num--;
		}else {
			System.out.println("Tickets sold out...");
		}
	}
}

public class ThreadSafety extends Thread{
	

	public static void main(String[] args) {
		//用 Thread 子对象的方式实现多线程
//		TicketMachine machine1 = new TicketMachine("北京东");
//		TicketMachine machine2 = new TicketMachine("北京南");
//		TicketMachine machine3 = new TicketMachine("北京西");
//		TicketMachine machine4 = new TicketMachine("北京北");
//		
//		machine1.start();
//		machine2.start();
//		machine3.start();
//		machine4.start();
		
		//用实现 Runnable 接口方式实现多线程
		TM tm = new TM();
		new Thread(tm, "TM1").start();
		new Thread(tm, "TM2").start();
		new Thread(tm, "TM3").start();
	}
}
